package com.custom.view;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.EdgeEffect;
import android.widget.OverScroller;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2017/3/19.
 */

public class HorizontalIconView extends View {
    private static final String TAG = "HorizontalIconView";

    private static final int INVALID_POINTER = MotionEvent.INVALID_POINTER_ID;

    private int mActivePointerId = INVALID_POINTER;

    private List<Drawable> mDrawables;

    private EdgeEffectCompat mEdgeEffectLeft;

    private EdgeEffectCompat mEdgeEffectRight;

    private final List<Rect> mIconPositions = new ArrayList<>();

    private int mIconSize;

    private int mIconSpacing;

    private boolean mIsBeingDragged;

    private int mMaximumVelocity;

    private int mMinimumVelocity;

    /**
     * 在VIEW边界之外瞬移多远
     */
    private int mOverflingDistance;

    /**
     * 在VIEW边界之外滑动多远
     */
    private int mOverscrollDistances;

    private float mPreviousX = 0;

    /**
     * 这个VIEW可以滚动的像素数(基本是宽度-可见度)
     */
    private int mScrollRange;

    private int mTouchSlop;

    private VelocityTracker mVoVelocityTracker;

    /**
     * 用来处理流畅滑动复杂工作的Scroller
     */
    private OverScroller mScroller;

    private int mSkippedIconCount = 0;

    public HorizontalIconView(Context context) {
        super(context);
        init(context);
    }

    public HorizontalIconView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public HorizontalIconView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        final Resources res = context.getResources();
        mIconSize = res.getDimensionPixelSize(R.dimen.icon_size);
        mIconSpacing = res.getDimensionPixelSize(R.dimen.icon_spacing);

        final ViewConfiguration config = ViewConfiguration.get(context);
        mTouchSlop = config.getScaledTouchSlop();
        mMinimumVelocity = config.getScaledMinimumFlingVelocity();
        mMaximumVelocity = config.getScaledMaximumFlingVelocity();
        mOverflingDistance = config.getScaledOverflingDistance();
        mOverscrollDistances = config.getScaledOverscrollDistance();

        setWillNotDraw(false);

        mEdgeEffectLeft = new EdgeEffectCompat(context);
        mEdgeEffectRight = new EdgeEffectCompat(context);
        mScroller = new OverScroller(context);
        setFocusable(true);
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            int oldX = getScrollX();
            int x = mScroller.getCurrX();

            if (oldX != x) {
                overScrollBy(x - oldX, 0, oldX, 0, mScrollRange, 0, mOverflingDistance, 0, false);
                onScrollChanged(x, 0, oldX, 0);

                if (x < 0 && oldX >= 0) {
                    mEdgeEffectLeft.onAbsorb((int) mScroller.getCurrVelocity());
                } else if (x > mScrollRange && oldX <= mScrollRange) {
                    mEdgeEffectRight.onAbsorb((int) mScroller.getCurrVelocity());
                }
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mVoVelocityTracker == null) {
            mVoVelocityTracker = VelocityTracker.obtain();
        }
        mVoVelocityTracker.addMovement(event);

        int action = MotionEventCompat.getActionMasked(event);
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }

                mPreviousX = (int) MotionEventCompat.getX(event, 0);
                mActivePointerId = MotionEventCompat.getPointerId(event, 0);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                final int activePointerIndex = MotionEventCompat.findPointerIndex(event, mActivePointerId);
                if (activePointerIndex == INVALID_POINTER) {
                    break;
                }

                final int x = (int) MotionEventCompat.getX(event, 0);
                int deltax = (int) (mPreviousX - x);
                if (!mIsBeingDragged && Math.abs(deltax) > mTouchSlop) {
                    mIsBeingDragged = true;
                    if (deltax > 0) {
                        deltax -= mTouchSlop;
                    } else {
                        deltax += mTouchSlop;
                    }
                }

                if (mIsBeingDragged) {
                    mPreviousX = x;

                    final int oldX = getScrollX();
                    final int range = mScrollRange;

                    if (overScrollBy(deltax, 0, oldX, 0, range, 0, mOverscrollDistances, 0, true)) {
                        //如果我们到达了某个滚动界限则刹住速度值
                        mVoVelocityTracker.clear();
                    }
                    if (mEdgeEffectLeft != null) {
                        final int pulledToX = oldX + deltax;
                        final int y = (int) MotionEventCompat.getY(event, 0);
                        final float yDisplacement = 1 - ((float) y / getHeight());
                        if (pulledToX < 0) {
                            mEdgeEffectLeft.onPull((float) deltax / getWidth(), yDisplacement);
                            if (!mEdgeEffectRight.isFinished()) {
                                mEdgeEffectRight.onRelease();
                            }
                        } else if (pulledToX > range) {
                            mEdgeEffectRight.onPull((float) deltax / getWidth(), yDisplacement);
                            if (!mEdgeEffectLeft.isFinished()) {
                                mEdgeEffectLeft.onRelease();
                            }
                        }

                        if (!mEdgeEffectLeft.isFinished() || !mEdgeEffectRight.isFinished()) {
                            postInvalidateOnAnimation();
                        }

                    }
                }
                break;
            }
            case MotionEvent.ACTION_UP:
                if (mIsBeingDragged) {
                    mVoVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int initialVelocity = (int) mVoVelocityTracker.getXVelocity(mActivePointerId);
                    if (Math.abs(initialVelocity) > mMinimumVelocity) {
                        fling(-initialVelocity);
                    } else {
                        if (mScroller.springBack(getScrollX(), 0, 0, mScrollRange, 0, 0)) {
                            postInvalidateOnAnimation();
                        }
                    }

                    mActivePointerId = INVALID_POINTER;
                    mIsBeingDragged = false;
                    mVoVelocityTracker.recycle();
                    mVoVelocityTracker = null;

                    if (mEdgeEffectLeft != null) {
                        mEdgeEffectLeft.onRelease();
                        mEdgeEffectRight.onRelease();
                    }
                } else {
                    final int actionPointerIndex = event.findPointerIndex(mActivePointerId);
                    if (actionPointerIndex == INVALID_POINTER) {
                        return false;
                    }

                    final int x = (int) event.getX(actionPointerIndex) + getScrollX();
                    final int y = (int) event.getY(actionPointerIndex);
                    int i = 0;
                    for (Rect rect : mIconPositions) {
                        if (rect.contains(x, y)) {
                            final int posiiton = i + mSkippedIconCount;
                            Toast.makeText(getContext(), "Pressed icon" + posiiton + "; rect Count : " + mIconPositions.size(), Toast.LENGTH_SHORT).show();
                            break;
                        }
                        i++;
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL: {
                if (mIsBeingDragged) {
                    if (mScroller.springBack(getScrollX(), 0, 0, mScrollRange, 0, 0)) {
                        postInvalidateOnAnimation();
                        ;
                    }

                    mActivePointerId = INVALID_POINTER;
                    mIsBeingDragged = false;
                    if (mVoVelocityTracker != null) {
                        mVoVelocityTracker.recycle();
                        mVoVelocityTracker = null;
                    }

                    if (mEdgeEffectLeft != null) {
                        mEdgeEffectLeft.onRelease();
                        mEdgeEffectRight.onRelease();
                    }
                }
                break;
            }
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(event);
                break;
        }
        return true;
    }

    public void setDrawables(List<Drawable> drawables) {
        if (mDrawables == null) {
            if (drawables == null) {
                return;
            }
            requestLayout();
        } else if (drawables == null) {
            requestLayout();
            mDrawables = null;
            return;
        } else if (mDrawables.size() == drawables.size()) {
            invalidate();
        } else {
            requestLayout();
        }
        mDrawables = new ArrayList<>(drawables);
        mIconPositions.clear();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDrawables == null || mDrawables.isEmpty()) {
            return;
        }

        final int width = getWidth();
        final int height = getHeight();
        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();

        final int leftEdge = getScrollX();
        final int rightEdge = leftEdge + width;

        int left = paddingLeft;
        final int top = paddingTop;
        mSkippedIconCount = 0;
        final int iconCount = mDrawables.size();
        for (int i = 0; i < iconCount; i++) {
            if (left + mIconSize < leftEdge) {
                left = left + mIconSize + mIconSpacing;
                mSkippedIconCount++;
                continue;
            }

            if (left > rightEdge) {
                break;
            }

            final Drawable icon = mDrawables.get(i);
            icon.setBounds(left, top, left + mIconSize, top + mIconSize);
            icon.draw(canvas);

            final int drawnPosition = i - mSkippedIconCount;
            if (drawnPosition + 1 > mIconPositions.size()) {
                final Rect rect = icon.copyBounds();
                mIconPositions.add(rect);
            } else {
                final Rect rect = mIconPositions.get(drawnPosition);
                icon.copyBounds(rect);
            }
            left = left + mIconSize + mIconSpacing;
        }

        if (mEdgeEffectLeft != null) {
            if (!mEdgeEffectLeft.isFinished()) {
                final int restoreCount = canvas.save();
                canvas.rotate(270);
                canvas.translate(-height, Math.min(0, leftEdge));
                mEdgeEffectLeft.setSize(height, width);
                if (mEdgeEffectLeft.draw(canvas)) {
                    postInvalidateOnAnimation();
                    ;
                }
                canvas.restoreToCount(restoreCount);
            }

            if (!mEdgeEffectRight.isFinished()) {
                final int restoreCount = canvas.save();
                canvas.rotate(90);
                canvas.translate(0, -(Math.max(mScrollRange, leftEdge) + width));
                mEdgeEffectRight.setSize(height, width);
                if (mEdgeEffectRight.draw(canvas)) {
                    postInvalidateOnAnimation();
                }
                canvas.restoreToCount(restoreCount);
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        if (mScroller.isFinished()) {
            super.scrollTo(scrollX, scrollY);
        } else {
            setScrollX(scrollX);
            if (clampedX) {
                mScroller.springBack(scrollX, 0, 0, mScrollRange, 0, 0);
            }
        }
    }

    private void fling(int velocity) {
        if (mScrollRange == 0) {
            return;
        }

        final int halfWidth = ((getWidth() - getPaddingLeft() - getPaddingRight()) / 2);
        mScroller.fling(getScrollX(), 0, velocity, 0, 0, mScrollRange, 0, 0, halfWidth, 0);
        invalidate();
    }

    private int measureHeight(int measureSpec) {
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        int result;
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = mIconSize + getPaddingTop() + getPaddingBottom();
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    private int measureWidth(int measureSpec) {
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        final int icons = (mDrawables == null) ? 0 : mDrawables.size();
        final int iconSpace = mIconSize + icons;
        final int dividerSpace;

        if (icons <= 1) {
            dividerSpace = 0;
        } else {
            dividerSpace = (icons - 1) * mIconSpacing;
        }

        final int maxSize = dividerSpace + iconSpace + getPaddingLeft() + getPaddingRight();

        int result;
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(maxSize, specSize);
            } else {
                result = maxSize;
            }
        }

        if (maxSize > result) {
            mScrollRange = maxSize - result;
        } else {
            mScrollRange = 0;
        }
        return result;
    }

    private void onSecondaryPointerUp(MotionEvent event)
    {
        final int pointerIndex = MotionEventCompat.getActionIndex(event);
        final int pointerId = MotionEventCompat.getPointerId(event, pointerIndex);
        if(pointerId == mActivePointerId)
        {
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mPreviousX = event.getX(newPointerIndex);
            mActivePointerId = event.getPointerId(newPointerIndex);
            if(mVoVelocityTracker != null)
            {
                mVoVelocityTracker.clear();
            }
        }
    }
}
